import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Semaphore;

import javax.swing.JFrame;
import java.awt.GridLayout;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import java.awt.Rectangle;
import java.awt.Component;

public class simulator extends JFrame{
	final static int SLEEPING = 0;
	final static int WAITING = 1;
	final static int ENQUEUED = 3;
	final static int RUNNING = 4;
	
	final static int DEFAULTSLEEPTIME = 1000;
	
	final static String newLine = System.getProperty("line.separator");

	 private Semaphore mutex;
	 private Semaphore availableProducts;
	 private Semaphore availableSlots;

	 private Queue<Integer> products;
	 private int productIDCounter;

	 private JTextArea producerLog;
	 private JTextArea consumerLog;
	 
	 private JTextArea producerArea;
	 private JTextArea consumerArea;
	 private JTextArea statsArea;

	 private List<producer> producerList;
	 private List<consumer> consumerList;

	public simulator (int maxProducts, List<Integer> producerWaitTimes, List<Integer> consumerWaitTimes){
		this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		//Initialize values
		this.mutex = new Semaphore(1);
		this.availableProducts = new Semaphore(0);
		this.availableSlots = new Semaphore(maxProducts);
		this.products = new LinkedList<Integer>();
		this.productIDCounter = 0;

		//GUI
		this.setSize(980, 495);
		getContentPane().setLayout(null);
		this.setVisible(true);

		//Labels
		{			
			//Input
			JLabel lblInput = new JLabel("Input:");
			lblInput.setBounds(10, 11, 46, 14);
			getContentPane().add(lblInput);

			//Producer Activity
			JLabel producerActivity = new JLabel("Producer Activity:");
			producerActivity.setBounds(470, 11, 111, 14);
			getContentPane().add(producerActivity);
			
			//Consumer Activity
			JLabel lblConsumerActivity = new JLabel("Consumer Activity:");
			lblConsumerActivity.setBounds(718, 11, 111, 14);
			getContentPane().add(lblConsumerActivity);


			//Producers
			JLabel lblProducers = new JLabel("Producers:");
			lblProducers.setBounds(10, 227, 75, 14);
			getContentPane().add(lblProducers);
			
			//Consumers
			JLabel lblConsumers = new JLabel("Consumers:");
			lblConsumers.setBounds(240, 227, 75, 14);
			getContentPane().add(lblConsumers);

			//Stats
			JLabel lblStats = new JLabel("Products in Buffer:");
			lblStats.setBounds(240, 11, 126, 14);
			getContentPane().add(lblStats);
		}

		//Input Text Area
		{
			//Text Area
			JTextArea inputArea = new JTextArea();
			inputArea.setEditable(false);
			inputArea.setBounds(10, 36, 220, 180);
			getContentPane().add(inputArea);

			//Content
			StringBuilder builder = new StringBuilder();
			builder.append("Maximum Products: " + maxProducts);
			builder.append(newLine + newLine);
			for(int i = 0; i < producerWaitTimes.size(); i++){
				builder.append(String.format("Producer# %d sleep time = %d sec", i, producerWaitTimes.get(i)));
				builder.append(newLine);
			}
			builder.append(newLine);
			for(int i = 0; i < consumerWaitTimes.size(); i++){
				builder.append(String.format("Consumer# %d sleep time = %d sec", i, consumerWaitTimes.get(i)));
				builder.append(newLine);
			}
			inputArea.setText(builder.toString());
			inputArea.setCaretPosition(0);

			//Scroll Bar
			JScrollPane inputScroll = new JScrollPane(inputArea);
			inputScroll.setBounds(inputArea.getBounds());
			getContentPane().add(inputScroll);
		}

		//Producer Log Area
		{
			//Text Area
			producerLog = new JTextArea();
			producerLog.setText("");
			producerLog.setEditable(false);
			producerLog.setBounds(497, 202, 462, 196);
			getContentPane().add(producerLog);

			//Scroll Bar
			JScrollPane producerLogScroll = new JScrollPane(producerLog);
			producerLogScroll.setBounds(new Rectangle(470, 36, 238, 396));
			getContentPane().add(producerLogScroll);
		}
		//Consumer Log Area
		{
		//Text Area
		consumerLog = new JTextArea();
		consumerLog.setText("");
		consumerLog.setEditable(false);
		consumerLog.setBounds(718, 37, 236, 394);
		getContentPane().add(consumerLog);
		
		//Scroll Bar
		JScrollPane consumerLogScroll = new JScrollPane(consumerLog);
		consumerLogScroll.setBounds(new Rectangle(718, 37, 236, 394));
		getContentPane().add(consumerLogScroll);
		}
		//Producer
		{
			//Text Area
			producerArea = new JTextArea();
			producerArea.setEditable(false);
			producerArea.setBounds(10, 252, 220, 180);
			getContentPane().add(producerArea);

			//Scroll Bar
			JScrollPane producerScroll = new JScrollPane(producerArea);
			producerScroll.setBounds(producerArea.getBounds());
			getContentPane().add(producerScroll);
		}

		//Consumer
		{
			//Text Area
			consumerArea = new JTextArea();
			consumerArea.setEditable(false);
			consumerArea.setBounds(240, 252, 220, 180);
			getContentPane().add(consumerArea);
			
			//Scroll Bar
			JScrollPane consumerScroll = new JScrollPane(consumerArea);
			consumerScroll.setBounds(consumerArea.getBounds());
			getContentPane().add(consumerScroll);
		}
		
		//Stats
		{
			//Text Area
			statsArea = new JTextArea();
			statsArea.setEditable(false);
			statsArea.setBounds(240, 36, 220, 180);
			getContentPane().add(statsArea);
			
			//Scroll Bar
			JScrollPane statsScroll = new JScrollPane(statsArea);
			statsScroll.setBounds(statsArea.getBounds());
			getContentPane().add(statsScroll);
		}

		//Threads
		{
			//Producer and Consumer List
			producerList = new ArrayList<producer>();
			consumerList = new ArrayList<consumer>();

			//Initialize Threads
			List<Thread> producerThreadList = new ArrayList<Thread>();
			List<Thread> consumerThreadList = new ArrayList<Thread>();

			//Output
			for(int i = 0 ; i < producerWaitTimes.size(); i++){
				producerList.add(new producer(i, producerWaitTimes.get(i)));
				producerThreadList.add(new Thread(this.new producerRun(producerList.get(i))));
			}
			for(int i = 0 ; i < consumerWaitTimes.size(); i++){
				consumerList.add(new consumer(i, consumerWaitTimes.get(i)));
				consumerThreadList.add(new Thread(this.new consumerRun(consumerList.get(i))));
			}

			//Start Threads
			for(Thread t : producerThreadList){
				t.start();
			}
			for(Thread t : consumerThreadList){
				t.start();
			}

			//Producer Monitor
			Thread prodMonitor = new Thread(this.new producerMonitor());
			prodMonitor.start();
			//Consumer Monitor
			Thread consMonitor = new Thread(this.new consumerMonitor());
			consMonitor.start();
			
			//Stats Monitor
			Thread statsMonitor = new Thread(this.new statsMonitor());
			statsMonitor.start();
		}
	}

	private class statsMonitor implements Runnable{
		public void run(){
			while(true){
				StringBuilder builder = new StringBuilder();
//				builder.append("Products Left: " + availableProducts.availablePermits() + newLine);
//				builder.append("Slots Left: " + availableSlots.availablePermits() + newLine);
//				builder.append(newLine);
//				builder.append("Products in Buffer:");
//				builder.append(newLine);
				builder.append("Available Products: "  + products.size() + newLine);
				builder.append(newLine);
				for(Integer productNumber : products){
					builder.append("Product # " + productNumber + newLine);
				}
				statsArea.setText(builder.toString());
				try {
					Thread.sleep(DEFAULTSLEEPTIME/10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
	
	class producer{

		private int producerID;
		private int waitTime;
		private int state;
		private int timer;

		public producer(int producerID, int waitTime){
			this.producerID = producerID;
			this.waitTime = waitTime;
			setState(SLEEPING);
		}

		public int getProducerID(){
			return producerID;
		}

		public int getWaitTime(){
			return waitTime;
		}

		public int getState() {
			return state;
		}

		public void setState(int state) {
			this.state = state;
		}

		public int getTimer() {
			return timer;
		}

		public void setTimer(int timer) {
			this.timer = timer;
		}
		public void decrementTimer(){
			timer--;
		}
		
	}

	private class producerMonitor implements Runnable{
		public void run(){
			while(true){
				StringBuilder builder = new StringBuilder();
				for(producer prod : producerList){
					builder.append("Producer # " + prod.getProducerID() + " ");
					switch(prod.getState()){
					case SLEEPING : builder.append("SLEEPING"); 
					builder.append(" (" + prod.getTimer() + " sec)");
					break;
					case WAITING : builder.append("WAITING"); break;
					case ENQUEUED : builder.append("ENQUEUED"); break;
					case RUNNING : builder.append("RUNNNING"); break;
					}
					builder.append(newLine);
				}
				producerArea.setText(builder.toString());
				try {
					Thread.sleep(DEFAULTSLEEPTIME/10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	private class producerRun implements Runnable{
		private producer prod;
		public producerRun(producer prod){
			this.prod = prod;
		}
		public void run(){
			while(true){
				try{
					producerList.get(prod.getProducerID()).setState(SLEEPING);
					producerList.get(prod.getProducerID()).setTimer(prod.getWaitTime());
					while(producerList.get(prod.getProducerID()).getTimer() > 0){
						producerList.get(prod.getProducerID()).decrementTimer();
						Thread.sleep(1000);
					}
					Thread.sleep(DEFAULTSLEEPTIME);
					
					producerList.get(prod.getProducerID()).setState(WAITING);
					Thread.sleep(DEFAULTSLEEPTIME);
					
					availableSlots.acquire();
					producerList.get(prod.getProducerID()).setState(ENQUEUED);
					Thread.sleep(DEFAULTSLEEPTIME);
					
					mutex.acquire();

					producerList.get(prod.getProducerID()).setState(RUNNING);
					Thread.sleep(DEFAULTSLEEPTIME);
					producerLog.setText(producerLog.getText() + (String.format("Producer #%d Produced Product #%d", prod.getProducerID(), productIDCounter, prod.getWaitTime())) + newLine);
					producerLog.setCaretPosition(producerLog.getText().length());
					products.add(productIDCounter++);

					mutex.release();
					availableProducts.release();
					
				}catch(Exception e){

				}
			}
		}
	}

	class consumer{
		private int consumerID;
		private int waitTime;
		private int state;
		private int timer;

		public consumer(int consumerID, int waitTime){
			this.consumerID = consumerID;
			this.waitTime = waitTime;
			setState(SLEEPING);
		}

		public int getConsumerID(){
			return consumerID;
		}

		public int getWaitTime(){
			return waitTime;
		}

		public int getState() {
			return state;
		}

		public void setState(int state) {
			this.state = state;
		}

		public int getTimer() {
			return timer;
		}

		public void setTimer(int timer) {
			this.timer = timer;
		}
	}
	
	private class consumerMonitor implements Runnable{
		public void run(){
			while(true){
				StringBuilder builder = new StringBuilder();
				for(consumer cons: consumerList){
					builder.append("Consumer # " + cons.getConsumerID() + " ");
					switch(cons.getState()){
					case SLEEPING : builder.append("SLEEPING"); 
					builder.append(" (" + cons.getTimer() + " sec)");
					break;
					case WAITING : builder.append("WAITING"); break;
					case ENQUEUED : builder.append("ENQUEUED"); break;
					case RUNNING : builder.append("RUNNNING"); break;
					}
					builder.append(newLine);
				}
				consumerArea.setText(builder.toString());
				try {
					Thread.sleep(DEFAULTSLEEPTIME/10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}


	private class consumerRun implements Runnable{
		private consumer cons;
		private consumerRun(consumer cons){
			this.cons = cons;
		}	
		public void run(){
			while(true){
				try{	
					consumerList.get(cons.getConsumerID()).setState(SLEEPING);
					consumerList.get(cons.getConsumerID()).setTimer(cons.getWaitTime());
					while(consumerList.get(cons.getConsumerID()).getTimer() > 0){
						consumerList.get(cons.getConsumerID()).setTimer(consumerList.get(cons.getConsumerID()).getTimer() - 1);
						Thread.sleep(1000);
					}
					Thread.sleep(DEFAULTSLEEPTIME);
					
					consumerList.get(cons.getConsumerID()).setState(WAITING);
					Thread.sleep(DEFAULTSLEEPTIME);
					
					availableProducts.acquire();
					consumerList.get(cons.getConsumerID()).setState(ENQUEUED);
					Thread.sleep(DEFAULTSLEEPTIME);
					
					mutex.acquire();
					
					consumerList.get(cons.getConsumerID()).setState(RUNNING);
					Thread.sleep(DEFAULTSLEEPTIME);
					consumerLog.setText(consumerLog.getText() + (String.format("Consumer #%d Consumed Product #%d", cons.getConsumerID(), products.remove())) + newLine);
					consumerLog.setCaretPosition(consumerLog.getText().length());
					
					mutex.release();
					availableSlots.release();				
				}catch(Exception e){

				}
			}
		}
	}
}
